package qy.qyalgotrader.ts.barfeed;

import com.google.common.math.DoubleMath;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.math3.stat.StatUtils;
import org.apache.commons.math3.stat.descriptive.moment.Mean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import qy.jalgotrade.bar.Bar;
import qy.jalgotrade.bar.Bar.Frequency;
import qy.jalgotrade.bar.Bars;
import qy.jalgotrade.bar.BasicBar;
import qy.jalgotrade.barfeed.MemBarFeed;
import qy.jalgotrade.utils.CommonUtils;
import qy.qyalgotrader.utils.CommonDataUtils;
import qy.qyalgotrader.utils.Constants;
import qy.qyalgotrader.utils.Constants.ReplayMultiples;
import tech.tablesaw.api.ColumnType;
import tech.tablesaw.api.Row;
import tech.tablesaw.api.Table;
import tech.tablesaw.io.csv.CsvReadOptions;

import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.util.*;

/**
 * @author qy
 *
 */
public class TushareCsvFeed extends MemBarFeed {

	private static final Logger logger = LoggerFactory.getLogger(TushareCsvFeed.class);

	private final List<String> __identifiers;

	/**
	 * 对于无成交的 Bar, 是否生成 volume 为 0 的 Empty Bar:
	 */
	private final boolean __genEmptyBar;

	/**
	 * 是否生成 集合竞价 Bar: TODO: Deprecated
	 */
	@SuppressWarnings("unused")
	@Deprecated
	private final boolean __genCaBar;

	/**
	 * 是否采用 快进复盘模式, 以及快进倍数:
	 */
	private final Pair<Boolean, ReplayMultiples> __slowReplayMode;

	/**
	 *
	 * @param frequency frequency
	 * @param identifiers identifiers
	 * @param genEmptyBarP default: true
	 * @param genCaBarP default: false
	 * @param maxLenP maxLenP default: 0
	 * @param slowReplayModeP slowReplayModeP default: (false, ReplayMultiples.X100)
	 * @throws Exception Exception
	 */
	public TushareCsvFeed(Frequency frequency, List<String> identifiers,
						  Optional<Boolean> genEmptyBarP, Optional<Boolean> genCaBarP, OptionalInt maxLenP,
						  Optional<Pair<Boolean, ReplayMultiples>> slowReplayModeP) throws Exception {

		this(frequency, identifiers, genEmptyBarP.orElse(true), genCaBarP.orElse(false), maxLenP.orElse(0),
				slowReplayModeP.orElse(Pair.of(false, ReplayMultiples.X100)));
	}

	/**
	 * @param frequency frequency
	 * @param identifiers identifiers
	 * @param genEmptyBar genEmptyBar
	 * @param genCaBar genCaBar
	 * @param maxLen maxLen
	 * @param slowReplayMode slowReplayMode
	 * @throws Exception Exception
	 */
	public TushareCsvFeed(Frequency frequency, List<String> identifiers, boolean genEmptyBar, boolean genCaBar, int maxLen,
						  Pair<Boolean, ReplayMultiples> slowReplayMode) throws Exception {

		super(frequency, maxLen);

		__identifiers = identifiers;
		__genEmptyBar = genEmptyBar;
		__genCaBar = genCaBar;
		assert slowReplayMode != null && slowReplayMode.getRight() != null;
		__slowReplayMode = slowReplayMode;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see qy.jalgotrade.barfeed.BaseBarFeed#barsHaveAdjClose()
	 */
	@Override
	public boolean barsHaveAdjClose() {

		return false;
	}

	@Override
	public Bars getNextBars() throws Exception {

		// TODO: 目前只是简单地调用 Thread.sleep() 实现:
		if (!__slowReplayMode.getLeft()) {
			return super.getNextBars();
		} else {
			try {
				return super.getNextBars();
			} finally {
				long delayInMs = (long) (getFrequency().getValue() / (double) __slowReplayMode.getRight().getMultiple() * 1000);
				try {
					Thread.sleep(delayInMs);
				} catch (InterruptedException e) {
					logger.warn("InterruptedException occured: {}", ExceptionUtils.getStackTrace(e));
				}
			}
		}
	}

	public int loadBarsFromCsv(LocalDate fromDate, LocalDate toDateP, Map<String, String> idsWcMapP, boolean preferTickCsv, String csvFileEncP) {

		// TODO:
		return 0;
	}

	/**
	 * 返回总计交易日:
	 * 
	 * @param fromDate fromDate
	 * @param toDateP default: null
	 * @param tickCsvBaseP default: CommonDataUtils.TS_HIS_DATA_BASE_DIR_DEFAULT
	 * @param idsWcMapP default: null
	 * @param csvFileEncP default: "UTF-8"
	 * @return 总计交易日
	 * @throws Exception Exception
	 */
	public int loadBarsFromTickCsvs(LocalDate fromDate, Optional<LocalDate> toDateP, Optional<Path> tickCsvBaseP,
			Optional<Map<String, String>> idsWcMapP, Optional<String> csvFileEncP) throws Exception {

		LocalDate toDate = toDateP.orElse(null);
		Path tickCsvBase = tickCsvBaseP.orElse(CommonDataUtils.TS_HIS_DATA_BASE_DIR_DEFAULT);
		Map<String, String> idsWcMap = idsWcMapP.orElse(null);
		String csvFileEnc = csvFileEncP.orElse("UTF-8");

		return loadBarsFromTickCsvs(fromDate, toDate, tickCsvBase, idsWcMap, csvFileEnc);
	}

	/**
	 * 返回总计交易日:
	 * 
	 * @param fromDate fromDate
	 * @param toDate toDate
	 * @param tickCsvBase tickCsvBase
	 * @param idsWcMap idsWcMap
	 * @return 总计交易日
	 * @throws Exception Exception
	 */
	public int loadBarsFromTickCsvs(LocalDate fromDate, LocalDate toDate, Path tickCsvBase,
	        Map<String, String> idsWcMap, String csvFileEnc) throws Exception {

		List<LocalDate> tds = new ArrayList<>();
		if (toDate == null) {
			tds.add(fromDate);
		} else {
			tds = CommonDataUtils.tradingDaysBy(fromDate, toDate);
		}

		for (LocalDate e : tds) {
			__addBarsFromTickCsv(e, tickCsvBase, idsWcMap, ".csv", csvFileEnc);
		}

		return tds.size();
	}

	/**
	 * 
	 * @param fromDate fromDate
	 * @param toDateP default: fromDate
	 * @param csvBaseP default: WindDataUtils.WIND_HIS_DATA_BASE_DIR_DEFAULT
	 * @param tickCsvBaseP default: WindDataUtils.WIND_TICK_CSV_BASE_DEFAULT
	 * @param idsWcMapP: default: null
	 * @param csvFileEncP: default: "UTF-8"
	 * @return 返回总计交易日
	 * @throws Exception Exception
	 */
	public int loadBarsFromMinutesCsvs(LocalDate fromDate, Optional<LocalDate> toDateP,
			Optional<Path> csvBaseP, Optional<Path> tickCsvBaseP,
			Optional<Map<String, String>> idsWcMapP, Optional<String> csvFileEncP) throws Exception {

		LocalDate toDate = toDateP.orElse(fromDate);
		Path csvBase = csvBaseP.orElse(CommonDataUtils.TS_HIS_DATA_BASE_DIR_DEFAULT);
		Path tickCsvBase = tickCsvBaseP.orElse(CommonDataUtils.TS_TICK_CSV_BASE_DEFAULT);
		Map<String, String> idsWcMap = idsWcMapP.orElse(null);
		String csvFileEnc = csvFileEncP.orElse("UTF-8");

		return loadBarsFromMinutesCsvs(fromDate, toDate, csvBase, tickCsvBase, idsWcMap, csvFileEnc);
	}

	/**
	 * 
	 * @param fromDate fromDate
	 * @param toDate toDate
	 * @param csvBase csvBase
	 * @param tickCsvBase tickCsvBase
	 * @param idsWcMap idsWcMap
	 * @param csvFileEnc csvFileEnc
	 * @return 返回总计交易日
	 * @throws Exception Exception
	 */
	public int loadBarsFromMinutesCsvs(LocalDate fromDate, LocalDate toDate, Path csvBase, Path tickCsvBase,
	        Map<String, String> idsWcMap, String csvFileEnc) throws Exception {

		List<LocalDate> tds = CommonDataUtils.tradingDaysBy(fromDate, toDate);

		for (LocalDate e : tds) {
			__addBarsFromMinutesCsv(e, csvBase, tickCsvBase, idsWcMap, ".csv", csvFileEnc);
		}
		return tds.size();
	}

	/**
	 * TODO: 使用股票指数替代股指期货的交易时间不同步问题.
	 * 
	 * @param theDate theDate
	 * @param csvBase csvBase
	 * @param tickCsvBase tickCsvBase
	 * @param idsWcMap idsWcMap
	 * @param csvFileNameExt csvFileNameExt
	 * @param csvFileEnc csvFileEnc
	 * @throws Exception Exception
	 */
	private void __addBarsFromMinutesCsv(LocalDate theDate, Path csvBase, Path tickCsvBase,
	        Map<String, String> idsWcMap, String csvFileNameExt, String csvFileEnc) throws Exception {

		for (String e : __identifiers) {
			// 一一读取指定交易日的 (分钟级别: min_1, min_3, min_5, ...) 历史行情 csv:
			String idsWc = e;
			if (!CommonDataUtils.isWindCode(e)) {
				idsWc = CommonDataUtils.cnFuturesCodeToWindCode(e);
			}
			if (idsWcMap != null) {
				idsWc = idsWcMap.get(idsWc);
			}

			String exchId = Constants.exchSffxToName(CommonDataUtils.windCodeTuple(idsWc).getRight());

			String dtStr = theDate.format(Constants.DATE_FORMAT_YYYYMMDD);
			String dtYearStr = theDate.format(Constants.DATE_FORMAT_YYYY);
			Path aCsvFileP = csvBase.resolve(CommonDataUtils.barFreqToHisDataDir(getFrequency())).resolve(dtYearStr)
			        .resolve(dtStr).resolve(idsWc + csvFileNameExt);
			Map<String, Object> tdInfos = CommonDataUtils.getTradeInfos(idsWc, theDate);

			if (aCsvFileP.toFile().exists()) {

				Table df;
				try (InputStreamReader reader = new InputStreamReader(new FileInputStream(aCsvFileP.toFile()),
				        Charset.forName(csvFileEnc))) {

					// @formatter:off
					Map<String, ColumnType> colsTypeMap = MapUtils.putAll(new HashMap<>(), new Object[][] {
						{ Constants.DATE_TIME, ColumnType.LOCAL_DATE_TIME },
						{ Constants.BAR_FIELD_OPEN, ColumnType.DOUBLE },
						{ Constants.BAR_FIELD_CLOSE, ColumnType.DOUBLE },
						{ Constants.BAR_FIELD_HIGH, ColumnType.DOUBLE },
						{ Constants.BAR_FIELD_LOW, ColumnType.DOUBLE },
						{ Constants.BAR_FIELD_VOLUME, ColumnType.DOUBLE },
						{ Constants.BAR_FIELD_AMOUNT, ColumnType.DOUBLE }
					});
					ColumnType[] types = CommonUtils.genColumnsInfoFor(aCsvFileP, colsTypeMap, csvFileEnc);
					// @formatter:on
					CsvReadOptions options = CsvReadOptions.builder(reader).missingValueIndicator("").columnTypes(types)
					        .dateTimeFormat(Constants.DATE_TIME_FORMAT_STD).build();

					df = Table.read().usingOptions(options);
				}

				List<Bar> barList = new ArrayList<>();

				Mean meanUtils = new Mean();
				double[] closes = df.doubleColumn(Constants.BAR_FIELD_CLOSE).asDoubleArray();
				double[] vols = df.doubleColumn(Constants.BAR_FIELD_VOLUME).asDoubleArray();
				double[] amts = df.doubleColumn(Constants.BAR_FIELD_AMOUNT).asDoubleArray();
				for (int i = 0; i < df.rowCount(); i++) {
					/*
					 * Bar 均价 (vwap) = amount / volume / tradeUnit:
					 *     1. 沪深交易所指数: 不可用;
					 *     2. 郑商所 wsi min_1 数据: 不可用 (0.0, 由于 amount 为 0).
					 * 日内分时均价 (vwap_intraday):
					 *     1. 沪深交易所指数: close 加权平均 (按 amount 加权);
					 *     2. 郑商所 wsi min_1 数据: close 加权平均 (按 volume 加权).
					 * 
					 * TODO: 仅 min_1 频率 Bar 计算 vwap_intraday:
					 */
					Row aRow = df.row(i);
					ZonedDateTime dm = CommonDataUtils.toMarketDatetime(aRow.getDateTime(Constants.DATE_TIME));
					double open = aRow.getDouble(Constants.BAR_FIELD_OPEN);
					double high = aRow.getDouble(Constants.BAR_FIELD_HIGH);
					double low = aRow.getDouble(Constants.BAR_FIELD_LOW);
					double close = aRow.getDouble(Constants.BAR_FIELD_CLOSE);
					double vol = aRow.getDouble(Constants.BAR_FIELD_VOLUME);
					double amt = aRow.getDouble(Constants.BAR_FIELD_AMOUNT);
					// Bar VWAP = amount / volume / tradeUnit (郑商所计算值为 0.0):
					double vwap = Double.NaN;
					double vwapIntraday = Double.NaN;
					if (DoubleMath.fuzzyEquals(vol, 0.0, CommonUtils.FUZZY_EQUAL_TOLERANCE_MATH)) {
						vwap = amt / vol / (double) tdInfos.get("tradeUnit");
					}
					if (Arrays.asList("SSE", "SZSE").contains(exchId) && CommonDataUtils.isSseSzseIndex(idsWc)) {
						// 沪深交易所指数 Bar vwap 和 vwap_intraday:
						// vwap: NaN
						// vwap_intraday: close 加权平均 (按 amount 加权):
						vwapIntraday = meanUtils.evaluate(closes, amts, 0, i + 1);
					} else if (exchId.equals("CZCE")) {
						// 郑商所 Bar vwap 和 vwap_intraday:
						// vwap: 0.0
						// vwap_intraday: close 加权平均 (按 volume 加权):
						vwapIntraday = meanUtils.evaluate(closes, vols, 0, i + 1);
					} else {
						double amtAccu = StatUtils.sum(amts, 0, i + 1);
						double volAccu = StatUtils.sum(vols, 0, i + 1);
						if (!DoubleMath.fuzzyEquals(volAccu, 0.0, CommonUtils.FUZZY_EQUAL_TOLERANCE_MATH)) {
							vwapIntraday = amtAccu / volAccu / (double) tdInfos.get("tradeUnit");
						}
					}

					// @formatter:off
					Bar aBar = new BasicBar(dm, open, high, low, close, vol, close, getFrequency(),
					        MapUtils.putAll(new HashMap<>(), new Object[][] {
					        	{ Constants.BAR_FIELD_AMOUNT, amt },
					            { Constants.BAR_FIELD_VWAP, vwap },
					            { Constants.BAR_FIELD_VWAP_INTRADAY, vwapIntraday },
					        }));
					// @formatter:on
					barList.add(aBar);
				}

				if (barList.size() > 0) {
					addBarsFromSequence(e, barList);
				} else {
					logger.warn("EMPTY history minutes ({}) csv for {} ! loading bars from tick csv...", getFrequency(),
					        idsWc);
					__addBarsFromTickCsv(theDate, tickCsvBase, idsWcMap, csvFileNameExt, csvFileEnc);
				}
			} else {
				logger.warn("history minutes ({}) csv for {} does NOT exists! loading bars from tick csv...",
						getFrequency(), idsWc);
				__addBarsFromTickCsv(theDate, tickCsvBase, idsWcMap, csvFileNameExt, csvFileEnc);
			}
		}
	}

	/**
	 * 
	 * @param theDate theDate
	 * @param tickCsvBase tickCsvBase
	 * @param idsWcMap idsWcMap
	 * @param csvFileNameExt csvFileNameExt
	 * @param csvFileEnc csvFileEnc
	 */
	private void __addBarsFromTickCsv(LocalDate theDate, Path tickCsvBase, Map<String, String> idsWcMap,
	        String csvFileNameExt, String csvFileEnc) throws Exception {

		for (String e : __identifiers) {
			// 一一读取指定交易日的 Tick 历史行情 csv:
			String idsWc = e;
			if (!CommonDataUtils.isWindCode(e)) {
				idsWc = CommonDataUtils.cnFuturesCodeToWindCode(e);
			}
			if (idsWcMap != null) {
				idsWc = idsWcMap.get(idsWc);
			}

			String dtStr = theDate.format(Constants.DATE_FORMAT_YYYYMMDD);
			String dtYearStr = theDate.format(Constants.DATE_FORMAT_YYYY);
			Path aCsvFileP = tickCsvBase.resolve(dtYearStr).resolve(dtStr).resolve(idsWc + csvFileNameExt);

			if (aCsvFileP.toFile().exists()) {

				// @formatter:off
				Map<String, ColumnType> colsTypeMap = MapUtils.putAll(new HashMap<>(), new Object[][] {
					{ Constants.DATE_TIME, ColumnType.LOCAL_DATE_TIME },
					{ Constants.WIND_FIELD_NAME_MAPPING_WST.get("pre_close", "resp"), ColumnType.DOUBLE },
					{ Constants.WIND_FIELD_NAME_MAPPING_WST.get("last", "resp"), ColumnType.DOUBLE },
					{ Constants.WIND_FIELD_NAME_MAPPING_WST.get("volume", "resp"), ColumnType.DOUBLE },
					{ Constants.WIND_FIELD_NAME_MAPPING_WST.get("amt", "resp"), ColumnType.DOUBLE },
					{ Constants.WIND_FIELD_NAME_MAPPING_WST.get("oi", "resp"), ColumnType.DOUBLE }
				});
				ColumnType[] types = CommonUtils.genColumnsInfoFor(aCsvFileP, colsTypeMap, csvFileEnc);
				// @formatter:on
				Table df;
				try (InputStreamReader reader = new InputStreamReader(new FileInputStream(aCsvFileP.toFile()),
				        Charset.forName(csvFileEnc))) {

					CsvReadOptions options = CsvReadOptions.builder(reader).missingValueIndicator("").columnTypes(types)
					        .dateTimeFormat(Constants.DATE_TIME_FORMAT_MS).build();

					df = Table.read().usingOptions(options);

				}

				List<Bar> barList = __buildBarList(e, df, getFrequency(), theDate);
				if (barList.size() > 0) {
					addBarsFromSequence(e, barList);
				} else {
					logger.warn("EMPTY history Tick csv for {} ({}) !", idsWc, aCsvFileP);
				}
			} else {
				logger.warn("Tick csv for {} ({}) NOT found !", idsWc, aCsvFileP);
			}
		}
	}

	/**
	 * 根据指定的 周期, 对由 Wind 返回的 Tick 行情 DataFrame 数据进行采样, 返回一个 list (Bar); 若周期内无成交, 则使用 None 代替.
	 * 
	 * @param identifier identifier
	 * @param df df
	 * @param frequency frequency
	 * @param theDate theDate
	 * @return List&lt;Bar&gt;
	 */
	private List<Bar> __buildBarList(String identifier, Table df, Frequency frequency, LocalDate theDate)
	        throws Exception {

		List<Bar> barList = new ArrayList<>();

		// 过滤掉 datetime 不在交易时间范围内的 tick:
		IntArrayList indxes = new IntArrayList();
		df.forEach(row -> {
			if (CommonDataUtils.isIllegalCnFuTickDatetime(identifier, row.getDateTime(Constants.DATE_TIME),
					OptionalInt.empty(), Optional.empty(), Optional.empty(), Optional.empty())) {
				indxes.add(row.getRowNumber());
			}
		});
		if (indxes.size() > 0) {
			df = df.dropRows(indxes.toIntArray());
		}
		// 过滤重复 Tick (仅盘口有变动无成交的 Tick, 郑商所收盘 Tick, ...):
		double lstVol = 0.0;
		IntArrayList indxes1 = new IntArrayList();
		for (Row row : df) {
			double v = row.getDouble(
			        (String) Constants.WIND_FIELD_NAME_MAPPING_WST.get("volume", "resp"));
			if (DoubleMath.fuzzyEquals(v, lstVol, CommonUtils.FUZZY_EQUAL_TOLERANCE_MATH)) {
				indxes1.add(row.getRowNumber());
			}
			lstVol = v;
		}
		if (indxes1.size() > 0) {
			df = df.dropRows(indxes1.toIntArray());
		}

		String exchId = Constants.exchSffxToName(CommonDataUtils.windCodeTuple(identifier).getRight());
		// 用来计算 bar 的 涨跌 及 涨跌幅, 每新产生一个 bar 后更新, 初始值为前收盘价 (从 tickDf 中获取):
		double priceRefVal = df.row(0)
		        .getDouble((String) Constants.WIND_FIELD_NAME_MAPPING_WST.get("pre_close", "resp"));
		// 交易时间段等信息:
		Map<String, Object> tdInfos = CommonDataUtils.getTradeInfos(identifier, theDate);
		LocalDateTime startDm;
		if (CommonDataUtils.isCnFuTdNightMarketAvailable(identifier, theDate)) {
			// 中国期货: 当前交易日包含上一交易日夜盘:
			startDm = LocalDateTime.of(CommonDataUtils.tradingDaysOffset(theDate, 1),
			        (LocalTime) tdInfos.get("openTimeNt"));
		} else {
			startDm = LocalDateTime.of(theDate, (LocalTime) tdInfos.get("openTimeNt"));
		}
		LocalDateTime endDm = LocalDateTime.of(theDate, (LocalTime) tdInfos.get("closeTimePm"));
		// 收盘 (交易暂停) last tick 最大延迟时间 (秒):
		int dtds = CommonDataUtils.getCnFuLastTickDelay(identifier);

		// min1 bars:
		Table rsmpDf = CommonDataUtils.sampleDataFrameTicksToMinuteTs(identifier, theDate, df, startDm, endDm,
		        true, dtds);
		if (frequency != Frequency.MINUTE) {
			// minX bars:
			rsmpDf = CommonDataUtils.resampleDataFrameMinuteToTs(rsmpDf, startDm, endDm, frequency, priceRefVal);
		}

		Mean meanUtils = new Mean();
		double[] closes = rsmpDf.doubleColumn(Constants.BAR_FIELD_CLOSE).asDoubleArray();
		double[] vols = rsmpDf.doubleColumn(Constants.BAR_FIELD_VOLUME).asDoubleArray();
		double[] amts = rsmpDf.doubleColumn(Constants.BAR_FIELD_AMOUNT).asDoubleArray();
		for (int i = 0; i < rsmpDf.rowCount(); i++) {
			Row aRow = rsmpDf.row(i);
			/*
			 * Bar 均价 (vwap) = amount / volume / tradeUnit:
			 *     1. 沪深交易所指数: 不可用;
			 *     2. 郑商所 wsi min_1 数据: 不可用 (0.0, 由于 amount 为 0).
			 * 日内分时均价 (vwap_intraday):
			 *     1. 沪深交易所指数: close 加权平均 (按 amount 加权);
			 *     2. 郑商所 wsi min_1 数据: close 加权平均 (按 volume 加权).
			 * 
			 * TODO: 仅 min_1 频率 Bar 计算 vwap_intraday:
			 */
			ZonedDateTime dm = CommonDataUtils.toMarketDatetime(aRow.getDateTime(Constants.DATE_TIME));
			double open = aRow.getDouble(Constants.BAR_FIELD_OPEN);
			double high = aRow.getDouble(Constants.BAR_FIELD_HIGH);
			double low = aRow.getDouble(Constants.BAR_FIELD_LOW);
			double close = aRow.getDouble(Constants.BAR_FIELD_CLOSE);
			double vol = aRow.getDouble(Constants.BAR_FIELD_VOLUME);
			double amt = aRow.getDouble(Constants.BAR_FIELD_AMOUNT);
			// Bar VWAP = amount / volume / tradeUnit (郑商所计算值为 0.0):
			double vwap = Double.NaN;
			double vwapIntraday = Double.NaN;
			if (DoubleMath.fuzzyEquals(vol, 0.0, CommonUtils.FUZZY_EQUAL_TOLERANCE_MATH)) {
				vwap = amt / vol / (double) tdInfos.get("tradeUnit");
			}
			if (Arrays.asList("SSE", "SZSE").contains(exchId) && CommonDataUtils.isSseSzseIndex(identifier)) {
				// 沪深交易所指数 Bar vwap 和 vwap_intraday:
				// vwap: NaN
				// vwap_intraday: close 加权平均 (按 amount 加权):
				vwapIntraday = meanUtils.evaluate(closes, amts, 0, i + 1);
			} else { // 郑商所 minute bar amount 已在 WindDataUtils.sampleDataframeTicksToMinuteWind() 中补全
				// vwap_intraday
				double amtAccu = StatUtils.sum(amts, 0, i + 1);
				double volAccu = StatUtils.sum(vols, 0, i + 1);
				if (!DoubleMath.fuzzyEquals(volAccu, 0.0, CommonUtils.FUZZY_EQUAL_TOLERANCE_MATH)) {
					vwapIntraday = amtAccu / volAccu / (double) tdInfos.get("tradeUnit");
				}
			}

			// @formatter:off
			Bar aBar = new BasicBar(dm, open, high, low, close, vol, close, getFrequency(),
			        MapUtils.putAll(new HashMap<>(), new Object[][] {
			        	{ Constants.BAR_FIELD_AMOUNT, amt },
			        	{ Constants.BAR_FIELD_VWAP, vwap },
			            { Constants.BAR_FIELD_VWAP_INTRADAY, vwapIntraday },
			        }));
			// @formatter:on
			barList.add(aBar);
		}

		return barList;
	}
}
